/*
 * Copyright 2007-2010 Jeremy Brown.
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @author <a href="http://jeremy-brown.com">Jeremy Brown</a>
 */

package groovy.prefuse

import groovy.prefuse.factory.RendererFactory as RendererFactory
import groovy.prefuse.factory.*
import groovy.prefuse.impl.action.*
import groovy.prefuse.impl.controls.*
import groovy.prefuse.impl.layout.*

import groovy.swing.SwingBuilder

import prefuse.action.*
import prefuse.action.animate.*
import prefuse.action.assignment.*
import prefuse.action.distortion.*
import prefuse.action.filter.*
import prefuse.action.layout.*
import prefuse.action.layout.graph.*
import prefuse.activity.*
import prefuse.controls.*
import prefuse.data.event.*
import prefuse.data.expression.*
import prefuse.data.expression.parser.*
import prefuse.data.io.*
import prefuse.data.query.*
import prefuse.render.*
import prefuse.util.*
import prefuse.util.display.*
import prefuse.util.ui.*
import prefuse.visual.sort.*

import java.awt.event.*
import javax.swing.event.*

/**
 * A helper class for creating Prefuse visualizations using GroovyMarkup
 *
 * @author <a href="mailto:jeremy.r.brown@gmail.com">Jeremy Brown</a>
 * @version $Revision: 0 $
 */
public class PrefuseBuilder extends SwingBuilder
{

	public static final String PREFUSE_DEFAULT_GROUP = "_prefuseDefaultGroup"
    public static final String DEFAULT_PREFUSE_DEFAULT_GROUP = null
    def radialSpaceFillingTreeRegistered = false
	
   
	public PrefuseBuilder(boolean init = true) 
	{
		super(init)

        this[PREFUSE_DEFAULT_GROUP] = DEFAULT_PREFUSE_DEFAULT_GROUP
	}
	
	
    public void registerPrefuse()
	{
		addAttributeDelegate(PrefuseBuilder.&predicateAttributeDelegate)

		registerFactory("prefuse", new PrefuseFactory())

		registerFactory("pfTable", new TableFactory())
		registerFactory("pfColumn", new ColumnFactory())
		registerFactory("pfRow", new RowFactory())
		
		registerFactory("pfGraph", new GraphFactory())
		registerFactory("pfTree", new TreeFactory())
		registerFactory("pfNode", new NodeFactory())
		registerFactory("pfEdge", new EdgeFactory())

		registerFactory("pfVisualization", new VisualizationFactory())
		registerFactory("pfVisualTable", new VisualTableFactory())
		
		registerFactory("pfDecorator", new DecoratorFactory())

		registerFactory("pfRenderer", new groovy.prefuse.factory.RendererFactory())
			registerFactory("pfAxisRenderer", new groovy.prefuse.factory.RendererFactory(AxisRenderer))
			registerFactory("pfEdgeRenderer", new groovy.prefuse.factory.RendererFactory(EdgeRenderer))
			registerFactory("pfLabelRenderer", new groovy.prefuse.factory.RendererFactory(LabelRenderer))
			registerFactory("pfNullRenderer", new groovy.prefuse.factory.RendererFactory(NullRenderer))
			registerFactory("pfPolygonRenderer", new groovy.prefuse.factory.RendererFactory(PolygonRenderer))
			registerFactory("pfShapeRenderer", new groovy.prefuse.factory.RendererFactory(ShapeRenderer))

		registerFactory("pfRendererFactory", new RendererFactoryFactory())
			registerFactory("pfDefaultRendererFactory", new RendererFactoryFactory(DefaultRendererFactory))

		registerFactory("pfAction", new ActionFactory())
			registerFactory("pfGroupAction", new ActionFactory(GroupAction))
			registerFactory("pfItemAction", new ActionFactory(ItemAction))
			registerFactory("pfRepaintAction", new ActionFactory(RepaintAction))
			registerFactory("pfTreeRootAction", new ActionFactory(TreeRootAction))

		registerFactory("pfActionList", new ActionListFactory())
		
		registerFactory("pfListener", new ListenerFactory())
			registerFactory("pfActivityListener", new ListenerFactory(ActivityListener))
			registerFactory("pfColumnListener", new ListenerFactory(ColumnListener))
			registerFactory("pfComponentListener", new ListenerFactory(java.awt.event.ComponentListener))
			registerFactory("pfControlListener", new ListenerFactory(ControlAdapter))
			registerFactory("pfExpressionListener", new ListenerFactory(ExpressionListener))
			registerFactory("pfGraphListener", new ListenerFactory(GraphListener))
			registerFactory("pfItemBoundsListener", new ListenerFactory(ItemBoundsListener))
			registerFactory("pfMouseListener", new ListenerFactory(MouseListener))
			registerFactory("pfMouseInputListener", new ListenerFactory(MouseInputListener))
			registerFactory("pfMouseMotionListener", new ListenerFactory(MouseMotionListener))
			registerFactory("pfMouseWheelListener", new ListenerFactory(MouseWheelListener))
			registerFactory("pfPaintListener", new ListenerFactory(PaintListener))
			registerFactory("pfProjectionListener", new ListenerFactory(ProjectionListener))
			registerFactory("pfTableListener", new ListenerFactory(TableListener))
			registerFactory("pfTableReadListener", new ListenerFactory(TableReadListener))
			registerFactory("pfTupleSetListener", new ListenerFactory(TupleSetListener))
			registerFactory("pfUpdateListener", new ListenerFactory(UpdateListener))

		registerFactory("pfAnimator", new AnimatorFactory())
			registerFactory("pfArrayAnimator", new AnimatorFactory(ArrayAnimator))
			registerFactory("pfAxisLabelAnimator", new AnimatorFactory(AxisLabelAnimator))
			registerFactory("pfColorAnimator", new AnimatorFactory(ColorAnimator))
			registerFactory("pfFontAnimator", new AnimatorFactory(FontAnimator))
			registerFactory("pfLocationAnimator", new AnimatorFactory(LocationAnimator))
			registerFactory("pfPolarLocationAnimator", new AnimatorFactory(PolarLocationAnimator))
			registerFactory("pfQualityControlAnimator", new AnimatorFactory(QualityControlAnimator))
			registerFactory("pfSizeAnimator", new AnimatorFactory(SizeAnimator))
			registerFactory("pfVisibilityAnimator", new AnimatorFactory(VisibilityAnimator))
			
		registerFactory("pfAssignment", new AssignmentFactory())
			registerFactory("pfColorAction", new AssignmentFactory(ColorAction))
			registerFactory("pfDataColorAction", new AssignmentFactory(DataColorAction))
			registerFactory("pfDataShapeAction", new AssignmentFactory(DataShapeAction))
			registerFactory("pfDataSizeAction", new AssignmentFactory(DataSizeAction))
			registerFactory("pfFontAction", new AssignmentFactory(FontAction))
			registerFactory("pfShapeAction", new AssignmentFactory(ShapeAction))
			registerFactory("pfSizeAction", new AssignmentFactory(SizeAction))
			registerFactory("pfStrokeAction", new AssignmentFactory(StrokeAction))
			
		registerFactory("pfDistortion", new DistortionFactory())
			registerFactory("pfFisheyeDistortion", new DistortionFactory(FisheyeDistortion))
			registerFactory("pfBifocalDistortion", new DistortionFactory(BifocalDistortion))
		
		registerFactory("pfFilter", new FilterFactory())
			registerFactory("pfFisheyeTreeFilter", new FilterFactory(FisheyeTreeFilter))
			registerFactory("pfGraphDistanceFilter", new FilterFactory(GraphDistanceFilter))
			registerFactory("pfVisibilityFilter", new FilterFactory(VisibilityFilter))
		
		registerFactory("pfLayout", new LayoutFactory())
			registerFactory("pfAggregateLayout", new LayoutFactory(AggregateLayout))
			registerFactory("pfAxisLabelLayout", new LayoutFactory(AxisLabelLayout))
			registerFactory("pfAxisLayout", new LayoutFactory(AxisLayout))
			registerFactory("pfCircleLayout", new LayoutFactory(CircleLayout))
			registerFactory("pfCollapsedStackLayout", new LayoutFactory(CollapsedStackLayout))
			registerFactory("pfCollapsedSubtreeLayout", new LayoutFactory(CollapsedSubtreeLayout))
			registerFactory("pfGridLayout", new LayoutFactory(GridLayout))
			registerFactory("pfRandomLayout", new LayoutFactory(RandomLayout))
			registerFactory("pfSpecifiedLayout", new LayoutFactory(SpecifiedLayout))
			registerFactory("pfStackedAreaChart", new LayoutFactory(StackedAreaChart))
			registerFactory("pfBalloonTreeLayout", new LayoutFactory(BalloonTreeLayout))
			registerFactory("pfForceDirectedLayout", new LayoutFactory(ForceDirectedLayout))
			registerFactory("pfFruchtermanReingoldLayout", new LayoutFactory(FruchtermanReingoldLayout))
			registerFactory("pfNodeLinkTreeLayout", new LayoutFactory(NodeLinkTreeLayout))
			registerFactory("pfRadialTreeLayout", new LayoutFactory(RadialTreeLayout))
			registerFactory("pfSquarifiedTreeMapLayout", new LayoutFactory(SquarifiedTreeMapLayout))

		registerFactory("pfDisplay", new DisplayFactory())
		
		registerFactory("pfSorter", new SorterFactory())
			registerFactory("pfItemSorter", new SorterFactory(ItemSorter))
			registerFactory("pfTreeDepthItemSorter", new SorterFactory(TreeDepthItemSorter))
		
		registerFactory("pfControl", new ControlFactory())
			registerFactory("pfAggregateDragControl", new ControlFactory(AggregateDragControl))
			registerFactory("pfAnchorUpdateControl", new ControlFactory(AnchorUpdateControl))
			registerFactory("pfControlAdapter", new ControlFactory())
			registerFactory("pfDragControl", new ControlFactory(DragControl))
			registerFactory("pfFocusControl", new ControlFactory(FocusControl))
			registerFactory("pfHoverActionControl", new ControlFactory(HoverActionControl))
			registerFactory("pfNeighborHighlightControl", new ControlFactory(NeighborHighlightControl))
			registerFactory("pfPanControl", new ControlFactory(PanControl))
			registerFactory("pfRotationControl", new ControlFactory(RotationControl))
			registerFactory("pfToolTipControl", new ControlFactory(ToolTipControl))
			registerFactory("pfSubtreeDragControl", new ControlFactory(SubtreeDragControl))
			registerFactory("pfWheelZoomControl", new ControlFactory(WheelZoomControl))
			registerFactory("pfZoomControl", new ControlFactory(ZoomControl))
			registerFactory("pfZoomingPanControl", new ControlFactory(ZoomingPanControl))
			registerFactory("pfZoomToFitControl", new ControlFactory(ZoomToFitControl))
			
		registerFactory("pfPredicate", new PredicateFactory())
			
		registerFactory("pfQueryBinding", new QueryBindingFactory())
			registerFactory("pfListQueryBinding", new QueryBindingFactory(ListQueryBinding))
			registerFactory("pfRangeQueryBinding", new QueryBindingFactory(RangeQueryBinding))
			registerFactory("pfSearchQueryBinding", new QueryBindingFactory(SearchQueryBinding))
			
		registerFactory("pfJFastLabel", new UIFactory(JFastLabel))
		registerFactory("pfJSearchPanel", new UIFactory(JSearchPanel))
		registerFactory("pfJPrefuseTable", new UIFactory(JPrefuseTable))
		registerFactory("pfJRangeSlider", new UIFactory(JRangeSlider))
			registerFactory("pfJRangeVSlider", new UIFactory(JRangeSlider, [orientation:JRangeSlider.VERTICAL]))
			registerFactory("pfJRangeHSlider", new UIFactory(JRangeSlider, [orientation:JRangeSlider.HORIZONTAL]))

		//attribute delegate for List attributes for use with setters that take multiple values
		addAttributeDelegate(PrefuseBuilder.&spreadListAttributeDelegate)
    }

    public void registerRadialSpaceFillingTree()
    {
    	//Check for the presence of the Radial Space Filling Tree / DocuBurst Prefuse extensions
    	//Available at: http://www.cs.utoronto.ca/~ccollins/research/docuburst/index.html
    	if(isClassAvailable("ca.utoronto.cs.prefuseextensions.layout.StarburstLayout"))
    	{
    		registerFactory("pfMouseWheelControl", new ControlFactory(ca.utoronto.cs.prefuseextensions.control.MouseWheelControl))
    		registerFactory("pfStarburstLayout", new LayoutFactory(ca.utoronto.cs.prefuseextensions.layout.StarburstLayout))
    		registerFactory("pfArcLabelRenderer", new RendererFactory(ca.utoronto.cs.prefuseextensions.render.ArcLabelRenderer))
    		registerFactory("pfDecoratorLabelRenderer", new RendererFactory(ca.utoronto.cs.prefuseextensions.render.DecoratorLabelRenderer))
    		registerFactory("pfRotationLabelRenderer", new RendererFactory(ca.utoronto.cs.prefuseextensions.render.RotationLabelRenderer))
    		registerFactory("pfSectorRenderer", new RendererFactory(ca.utoronto.cs.prefuseextensions.render.SectorRenderer))
    		radialSpaceFillingTreeRegistered = true
    	}
    	if(isClassAvailable("ca.utoronto.cs.prefuseextensions.layout.WormholeLayout"))
    	{
    		registerFactory("pfStarburstLayout", new LayoutFactory(ca.utoronto.cs.prefuseextensions.layout.WormholeLayout))
    	}
    }


    public boolean isClassAvailable(className) 
    {
    	try
    	{
	    	def klass = Class.forName(className)
	    	if(klass != null)
	    	{
	    		return true
	    	}
	    	return false
    	}
    	catch(Exception e){println(e.toString()); return false}
    }

    
	
    /**
     * Do some overrides for standard component handlers, else use super
     */
    public void registerBeanFactory(String nodeName, Class klass)
	{
		super.registerBeanFactory(nodeName, klass)
    }
	
	/**
	 * Nodes with an id: attribute will be stored so they can be retrieved later
	 */
	 /*
    public static objectIDAttributeDelegate(def builder, def node, def attributes) {
        def theID = attributes.remove('id')
        if (theID) {
            builder.setVariable(theID, node)
        }
    }
	*/
	
	
	/**
	* Nodes with a predicate: attribute that is a String or GString will have the attribute transformed into a prefuse.data.expression.Predicate instead
	*/
	public static predicateAttributeDelegate(def builder, def node, def attributes)
	{
		if(attributes.predicate && !(attributes.predicate instanceof Predicate))
		{
			attributes.predicate = (Predicate)ExpressionParser.parse(attributes.predicate)
		}
		if(builder.context.predicate && !(builder.context.predicate instanceof Predicate))
		{
			builder.context.predicate = (Predicate)ExpressionParser.parse(builder.context.predicate)
		}
	}

	
	
	//
	public String setPrefuseDefaultGroup(String group)
	{
		this[PREFUSE_DEFAULT_GROUP] = group
	}
	
	
	//Chooses between a defaultGroup: attribute set in the visualization() and saved as PREFUSE_DEFAULT_GROUP, or the group: attribute on the current node
	public String pickPrefuseGroup(Map attributes)
	{
		def group
		if(attributes.group)
		{
			group = attributes.remove("group")
		}
		if(!group)
		{
			group = this[PREFUSE_DEFAULT_GROUP]
		}
		return group
	}
	

	
	/**
	 * Sometimes a setter method will take multiple parameters. The normal attribute handling
	 * doesn't handle this. So this method will try to run the setter method with multiple parameters.
	 * For example, this could handle a method like something.setSize(30, 45). As an attribute this would be size:[30,45].
	 * If it succeeds it will remove that attribute from the attribute list.
	 */
	public static spreadListAttributeDelegate(def builder, def node, def attributes)
	{
		def deleteList = []
		attributes.each{key, value->
			if(value instanceof List)
			{
				def setterMethod = ""
				try
				{
					setterMethod = "set" + key[0].toUpperCase() + (key.size() > 1 ? key[1..-1] : "")
					node."$setterMethod"(*value)
					println("$setterMethod(" + value + ")")
					deleteList << key
				}
				catch(Exception e)
				{
					//ignore exceptions
					println("PrefuseBuilder.spreadListAttributeDelegate error for $setterMethod(" + value + "):\r\n" + e.toString())
				}
			}
		}
		
		deleteList.each{key->
			attributes.remove(key)
		}
	}
	
	
	public static boolean mapContainsKeys(def map, def keyList)
	{
		for(key in keyList)
		{
			if(!(map.containsKey(key)))
			{
				return false
			}
		}
		return true
	}
}